package org.movee.net.cfg.parser.api.aspect;

import org.movee.net.cfg.parser.api.utils.ApiUtils;
import org.movee.net.cfg.parser.domain.api.ApiConstants;
import org.movee.net.cfg.parser.domain.api.ApiException;
import org.movee.net.cfg.parser.domain.api.ApiExceptionResponse;
import org.movee.net.cfg.parser.domain.api.ApiStatusCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 *
 *
 * @author 
 */
@Slf4j
public class BasicApiExceptionHandler {

    private final HttpServletRequest httpRequest;

    public BasicApiExceptionHandler(HttpServletRequest httpRequest) {
        this.httpRequest = httpRequest;
    }

    public ResponseEntity<ApiExceptionResponse> handleGenericExcetion(Throwable t) {
        ApiStatusCode statusCode = determineStatusCode(t);
        ApiExceptionResponse res = createExceptionResponse(t, statusCode);

        logException(t);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set(ApiConstants.X_API_REQUEST_ID, res.getRequestId());
        String date = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
        httpHeaders.set(ApiConstants.X_API_DATE, date);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<ApiExceptionResponse> responseEntity;
        responseEntity = new ResponseEntity<>(res, httpHeaders, statusCode.getHttpStatus());

        return responseEntity;
    }

    private ApiStatusCode determineStatusCode(Throwable t) {
        ApiStatusCode statusCode;
        if (t instanceof ApiException) {
            statusCode = ((ApiException) t).getApiStatusCode();
        } else if (t instanceof BindException || t instanceof ConstraintViolationException) {
            statusCode = ApiStatusCode.INVALID_QUERY_PARAM;
        } else {
            log.error("{}", ExceptionUtils.getStackTrace(t));
            statusCode = ApiStatusCode.INTERNAL_ERROR;
        }
        return statusCode;
    }

    private ApiExceptionResponse createExceptionResponse(Throwable t, ApiStatusCode statusCode) {
        ApiExceptionResponse res = new ApiExceptionResponse();

        String requestId = (String) httpRequest.getAttribute(ApiConstants.X_API_REQUEST_ID);
        if (!StringUtils.hasLength(requestId)) {
            requestId = UUID.randomUUID().toString();
        }

        res.setRequestId(requestId);
        res.setCode(statusCode.getCode());
        res.setMessage(statusCode.getMessage() + t.getMessage());

        return res;
    }

    private void logException(Throwable t) {
        String requestRealIp = ApiUtils.getClientRealIp(httpRequest);
        String requestEntry = (String) httpRequest.getAttribute(ApiConstants.API_REQUEST_ENTRY);
        if (!StringUtils.hasLength(requestEntry)) {
            if (t instanceof BindException) {
                requestEntry = ((BindException) t).getObjectName();
            }
        }

        Instant start = (Instant) httpRequest.getAttribute(ApiConstants.API_REQUEST_START);
        if (start == null) {
            log.info("clientRealIp: {}, finish {}, duration time: 2 ms", requestRealIp, requestEntry);
        } else {
            Duration duration = Duration.between(start, Instant.now());
            log.info("clientRealIp: {}, finish {}, duration time: {} ms",
                    requestRealIp, requestEntry, duration.toMillis());
        }
    }

}
